package com.lx.perferred.mall.security.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lX
 * @version JDK 8
 * @className JwtTokenUtil (此处以class为例)
 * @date 2024/11/30
 * @description JWT工具
 */
public class JwtTokenUtil {

  private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
  private static final String CLAIM_KEY_USERNAME = "sub";
  /**
   * 创建日志
   */
  private static final String CLAIM_KEY_CREATED = "created";

  @Value("${jwt.secret}")
  private String secret;
  @Value("${jwt.expiration}")
  private Long expiration;
  @Value("${jwt.tokenHead}")
  private String tokenHead;

  /**
   * 根据参数生成Token
   * @param claims
   * @return
   */
  private String generateToken(Map<String, Object> claims) {
    return Jwts.builder()
      .setClaims(claims)
      .setExpiration(generateExpirationDate())
      .signWith(SignatureAlgorithm.HS512, secret)
      .compact();
  }

  /**
   * 从token中获取JWT中的负载
   */
  private Claims getClaimsFromToken(String token) {
    Claims claims = null;
    try {
      claims = Jwts.parser()
        .setSigningKey(secret)
        .parseClaimsJws(token)
        .getBody();
    } catch (Exception e) {
      LOGGER.info("JWT格式验证失败:{}", token);
    }
    return claims;
  }

  /**
   * 生成token的过期时间
   */
  private Date generateExpirationDate() {
    return new Date(System.currentTimeMillis() + expiration * 1000);
  }

  /**
   * 从token中获取登录用户名
   */
  public String getUserNameFromToken(String token) {
    String username;
    try {
      Claims claims = getClaimsFromToken(token);
      username = claims.getSubject();
    } catch (Exception e) {
      LOGGER.error("从token获取用户名失败,异常信息:{}",e.getStackTrace());
      username = null;
    }
    return username;
  }

  /**
   * 校验Token是否有效
   * @param token
   * @param userDetails
   * @return
   */
  public boolean validateToken(String token, UserDetails userDetails) {
    String username = getUserNameFromToken(token);
    return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
  }

  /**
   * 判断token是否已经失效
   */
  private boolean isTokenExpired(String token) {
    Date expiredDate = getExpiredDateFromToken(token);
    return expiredDate.before(new Date());
  }

  /**
   * 从token中获取过期时间
   */
  private Date getExpiredDateFromToken(String token) {
    Claims claims = getClaimsFromToken(token);
    return claims.getExpiration();
  }

  /**
   * 根据用户信息生成token
   */
  public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
    claims.put(CLAIM_KEY_CREATED, new Date());
    return generateToken(claims);
  }

  /**
   * 当原来的token没过期时是可以刷新的
   *
   * @param oldToken 带tokenHead的token
   */
  public String refreshHeadToken(String oldToken) {
    if(StrUtil.isEmpty(oldToken)){
      return null;
    }
    String token = oldToken.substring(tokenHead.length());
    if(StrUtil.isEmpty(token)){
      return null;
    }
    //token校验不通过
    Claims claims = getClaimsFromToken(token);
    if(claims==null){
      return null;
    }
    //如果token已经过期，不支持刷新
    if(isTokenExpired(token)){
      return null;
    }
    //如果token在30分钟之内刚刷新过，返回原token
    if(tokenRefreshJustBefore(token,30*60)){
      return token;
    }else{
      claims.put(CLAIM_KEY_CREATED, new Date());
      return generateToken(claims);
    }
  }

  /**
   * 判断token在指定时间内是否刚刚刷新过
   * @param token 原token
   * @param time 指定时间（秒）
   */
  private boolean tokenRefreshJustBefore(String token, int time) {
    Claims claims = getClaimsFromToken(token);
    Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
    Date refreshDate = new Date();
    //刷新时间在创建时间的指定时间内
    if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){
      return true;
    }
    return false;
  }
}
